
import json
from abc import ABC, abstractmethod
from collections import namedtuple
from PyQt6.QtWidgets import QListWidgetItem, QPushButton, QHBoxLayout, QWidget, QVBoxLayout, QLabel
import requests
import os
import pprint

from tqdm import tqdm

from PyQt6 import QtCore, QtGui, QtWidgets
import sys
import logging
# 设置日志
logging.basicConfig(level=logging.ERROR, filename='app.log', filemode='w',
                    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
import traceback
from PyQt6.QtCore import QSettings, QDir
from PyQt6.QtWidgets import QWidget, QFileDialog, QMessageBox
MusicEntry = namedtuple('MusicEntry', ['author', 'title', 'url'])
import about
from about import Ui_Form
from PyQt6.QtCore import QRunnable, pyqtSignal, QObject

current_music_source = 'qq' # 默认使用QQ音乐

# 请求头
headers = {
    'accept': 'application/json, text/javascript, */*; q=0.01',
    'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6,ru;q=0.5',
    'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
    'origin': 'https://music.txqq.pro',
    'priority': 'u=1, i',
    'referer': 'https://music.txqq.pro/?name=%E6%99%B4%E5%A4%A9&type=qq',
    'sec-ch-ua': '"Microsoft Edge";v="125", "Chromium";v="125", "Not.A/Brand";v="24"',
    'sec-ch-ua-mobile': '?0',
    'sec-ch-ua-platform': '"Windows"',
    'sec-fetch-dest': 'empty',
    'sec-fetch-mode': 'cors',
    'sec-fetch-site': 'same-origin',
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0',
    'x-requested-with': 'XMLHttpRequest',
}

#平台映射
data_sources_map = {
    'qq': '腾讯QQ音乐',
    'kugou': '酷狗音乐',
    'kuwo': '酷我音乐',
    'baidu': '千千音乐',
    '1ting': '一听音乐',
    'migu': '咪咕音乐',
    'lizhi': '荔枝FM',
    'qingting': '蜻蜓FM',
    'ximalaya': '喜马拉雅',
    '5singyc': '5sing原创',
    '5sing_cover': '5singfc',
    'kg': '全民K歌',
}

# 下载类 继承自QRunnable
class DownloadWorkerSignals(QObject):
    finished = pyqtSignal()
    error = pyqtSignal(str)

class DownloadWorker(QRunnable):
    def __init__(self, music_entry, save_path):
        super().__init__()
        self.music_entry = music_entry
        self.save_path = save_path
        self.signals = DownloadWorkerSignals()

    def run(self):
        try:
            filename = f"{self.music_entry.title} - {self.music_entry.author}.mp3"
            filepath = os.path.join(self.save_path, filename)

            response = requests.get(self.music_entry.url, stream=True)
            response.raise_for_status()

            with open(filepath, 'wb') as file:
                for chunk in response.iter_content(chunk_size=8192):
                    file.write(chunk)
            self.signals.finished.emit()
        except Exception as e:
            self.signals.error.emit(str(e))

class WorkerSignals(QObject):
    finished = pyqtSignal(list)
    error = pyqtSignal(str)
    progress = pyqtSignal(int)

class SearchWorker(QRunnable):
    def __init__(self, ui_instance, name):
        super().__init__()
        self.ui_instance = ui_instance
        self.name = name
        self.signals = WorkerSignals()

    def run(self):
        try:
            results = self.ui_instance.search(self.name, self.signals.progress)
            self.signals.finished.emit(results)
        except Exception as e:
            self.signals.error.emit(str(e))
def ensure_save_path_set(window):
    """ 确保保存路径已经设置，如果没有则让用户选择并保存 """
    settings = QSettings('MyCompany', 'MyApp')
    save_path = settings.value('save_path')

    # 如果保存路径尚未设置，显示提示信息
    if not save_path:
        QMessageBox.information(window, 'First Time Setup',
                                'Welcome! This is your first time using the application. Please set your save directory.')
        # 接着让用户选择保存路径
        save_path = QFileDialog.getExistingDirectory(window, 'Select Save Directory')
        if save_path:
            settings.setValue('save_path', save_path)
        else:
            QMessageBox.warning(window, 'Warning', 'You must select a save directory.')
            sys.exit()
class MyWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.ui.MainWindow = self  # 将MainWindow实例传递给Ui_MainWindow
        self.setWindowTitle("TMF音乐下载器")
        #设置背景

        self.resize(1000, 790)
        ensure_save_path_set(self)
        self.ui.pushButton.clicked.connect(self.ui.start_search)
        self.ui.threadpool = QtCore.QThreadPool()


class AboutWindow(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.ui = Ui_Form()  # 创建Ui_Form的实例
        self.ui.setupUi(self)  # 调用setupUi方法来初始化UI


class Ui_MainWindow(object):
    def search(self, name,progress_callback):
        music_list = []
        count = 0
        total_pages = 10
        # 检查音乐源

        for i in range(1, 10):
            data = {
                'input': name,
                'filter': 'name',
                'type': current_music_source,
                'page': i,
            }
            res = requests.post('https://music.txqq.pro/', headers=headers, data=data)
            datas = json.loads(res.text)
            data_list = datas['data']
            for item in data_list:
                author = item.get('author', '未知')
                title = item.get('title', '未知')
                url = item.get('url', '')
                if url:
                    progress = int((i / total_pages) * 100)
                    progress_callback.emit(progress)
                    music_list.append(MusicEntry(author, title, url))
        return music_list
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(1060, 790)
        #设置背景
        MainWindow.setStyleSheet("background-image: url(image/avicii.png);"
                                 "QWidget { opacity: 0.5; }")
        MainWindow.setAnimated(False)
        self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.pushButton = QtWidgets.QPushButton(parent=self.centralwidget)
        self.pushButton.setGeometry(QtCore.QRect(700, 0, 75, 23))
        self.pushButton.setObjectName("pushButton")
        self.listWidget = QtWidgets.QListWidget(parent=self.centralwidget)
        self.listWidget.setGeometry(QtCore.QRect(360, 40, 331, 401))
        self.listWidget.setObjectName("listWidget")
        self.lineEdit = QtWidgets.QLineEdit(parent=self.centralwidget)
        self.lineEdit.setGeometry(QtCore.QRect(360, 0, 331, 21))
        self.lineEdit.setDragEnabled(True)
        self.lineEdit.setReadOnly(False)
        self.lineEdit.setObjectName("lineEdit")
        self.comboBox = QtWidgets.QComboBox(parent=self.centralwidget)
        self.comboBox.setGeometry(QtCore.QRect(790, 0, 121, 22))
        self.comboBox.setObjectName("comboBox")
        self.comboBox.addItem("")
        self.comboBox.addItem("")
        self.comboBox.addItem("")
        self.comboBox.addItem("")
        self.comboBox.addItem("")
        self.comboBox.addItem("")
        self.comboBox.addItem("")
        self.comboBox.addItem("")
        self.comboBox.addItem("")
        self.comboBox.addItem("")
        self.comboBox.addItem("")
        self.comboBox.addItem("")
        self.clear = QtWidgets.QPushButton(parent=self.centralwidget)
        self.clear.setGeometry(QtCore.QRect(710, 420, 75, 23))
        self.clear.setObjectName("clear")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 1060, 21))
        self.menubar.setObjectName("menubar")
        self.menu = QtWidgets.QMenu(parent=self.menubar)
        self.menu.setObjectName("menu")
        self.menu_3 = QtWidgets.QMenu(parent=self.menubar)
        self.menu_3.setObjectName("menu_3")
        self.menu_2 = QtWidgets.QMenu(parent=self.menubar)
        self.menu_2.setObjectName("menu_2")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(parent=MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)
        self.actionda = QtGui.QAction(parent=MainWindow)
        self.actionda.setObjectName("actionda")
        self.actionbaoc = QtGui.QAction(parent=MainWindow)
        self.actionbaoc.setObjectName("actionbaoc")
        self.about = QtGui.QAction(parent=MainWindow)
        self.about.setObjectName("action1111")
        self.menu.addAction(self.actionda)
        self.menu.addAction(self.actionbaoc)
        self.menu_2.addAction(self.about)
        self.menubar.addAction(self.menu.menuAction())
        self.menubar.addAction(self.menu_3.menuAction())
        self.menubar.addAction(self.menu_2.menuAction())

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)
        self.about.triggered.connect(self.show_about_window)
        #点击搜索打印搜索的歌曲名
        self.pushButton.clicked.connect(self.start_search)
        self.threadpool = QtCore.QThreadPool()

        self.progressBar = QtWidgets.QProgressBar(parent=self.centralwidget)
        self.progressBar.setGeometry(QtCore.QRect(360, 450, 331, 23))
        self.progressBar.setObjectName("progressBar")
        self.progressBar.setVisible(False)  # 初始时隐藏进度条


        # 添加状态标签
        self.statusLabel = QtWidgets.QLabel(parent=self.centralwidget)
        self.statusLabel.setGeometry(QtCore.QRect(360, 480, 331, 23))
        self.statusLabel.setObjectName("statusLabel")
        self.statusLabel.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
        self.statusLabel.setVisible(False)  # 初始时隐藏状态标签

        #点击清空按钮
        self.clear.clicked.connect(self.clear_list)

        #点击退出
        self.actionbaoc.triggered.connect(self.close)

  

    def clear_list(self):
         self.listWidget.clear()

    def start_search(self):
        name = self.lineEdit.text().strip()
        if not name:
            QMessageBox.warning(self, '警告', '请输入歌曲名!')
            return

        self.progressBar.setVisible(True)
        self.statusLabel.setVisible(True)
        self.statusLabel.setText("正在搜索...")

        worker = SearchWorker(self, name)
        worker.signals.finished.connect(self.update_search_results)
        worker.signals.error.connect(self.handle_search_error)
        worker.signals.progress.connect(self.update_progress)
        self.threadpool.start(worker)

    def update_progress(self, value):
        self.progressBar.setValue(value)



    def update_search_results(self, results):
        self.listWidget.clear()
        for result in results:
            # 创建一个包含歌曲信息和下载按钮的widget
            item_widget = QWidget()
            main_layout = QVBoxLayout()
            main_layout.setContentsMargins(5, 5, 5, 5)  # 设置外边距

            # 添加歌曲信息标签
            song_info = QLabel(f"{result.title} - {result.author}")
            song_info.setWordWrap(True)  # 允许文本换行
            main_layout.addWidget(song_info)

            # 创建一个水平布局来包含下载按钮
            button_layout = QHBoxLayout()
            button_layout.addStretch()  # 在左侧添加弹性空间

            # 添加下载按钮
            download_btn = QPushButton("下载")
            download_btn.setFixedSize(60, 25)  # 设置按钮固定大小
            download_btn.clicked.connect(lambda checked, r=result: self.download_music(r))
            button_layout.addWidget(download_btn)

            main_layout.addLayout(button_layout)

            item_widget.setLayout(main_layout)

            # 创建QListWidgetItem并设置其widget
            item = QListWidgetItem(self.listWidget)
            item.setSizeHint(item_widget.sizeHint())
            self.listWidget.addItem(item)
            self.listWidget.setItemWidget(item, item_widget)

            # 隐藏进度条和更新状态标签
        self.progressBar.setVisible(False)
        self.statusLabel.setText("搜索完成")

    def download_music(self, music_entry):
        settings = QSettings('MyCompany', 'MyApp')
        save_path = settings.value('save_path')

        if not save_path:
            QMessageBox.warning(self.MainWindow, '警告', '请先设置保存路径!')
            return

        try:
            worker = DownloadWorker(music_entry, save_path)
            worker.signals.finished.connect(self.download_finished)
            worker.signals.error.connect(self.download_error)
            self.threadpool.start(worker)
        except Exception as e:
            logging.error(f"Error starting download: {str(e)}")
            logging.error(traceback.format_exc())
            QMessageBox.critical(self.MainWindow, '错误', f'启动下载时发生错误: {str(e)}')

    def download_finished(self):
        QMessageBox.information(self.MainWindow, '成功', '下载完成!')

    def download_error(self, error_msg):
        QMessageBox.critical(self.MainWindow, '错误', f'下载失败: {error_msg}')

    def handle_search_error(self, error_msg):
        QMessageBox.critical(self, '错误', f'搜索时发生错误：{error_msg}')


    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate  #设置路径
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.pushButton.setText(_translate("MainWindow", "搜索"))
        self.lineEdit.setText(_translate("MainWindow", "请输入音乐名称"))
        self.lineEdit.setStyleSheet("QLineEdit { color: rgba(0, 0, 0, 60); }")
        self.comboBox.setItemText(0, _translate("MainWindow", "酷狗音乐"))
        self.comboBox.setItemText(1, _translate("MainWindow", "QQ音乐"))
        self.comboBox.setItemText(2, _translate("MainWindow", "千千音乐"))
        self.comboBox.setItemText(3, _translate("MainWindow", "一听音乐"))
        self.comboBox.setItemText(4, _translate("MainWindow", "5singfc"))
        self.comboBox.setItemText(5, _translate("MainWindow", "5sing原创"))
        self.comboBox.setItemText(6, _translate("MainWindow", "喜马拉雅"))
        self.comboBox.setItemText(7, _translate("MainWindow", "蜻蜓FM"))
        self.comboBox.setItemText(8, _translate("MainWindow", "荔枝FM"))
        self.comboBox.setItemText(9, _translate("MainWindow", "全民K歌"))
        self.comboBox.setItemText(10, _translate("MainWindow", "咪咕音乐"))
        self.comboBox.setItemText(11, _translate("MainWindow", "酷我音乐"))
        self.clear.setText(_translate("MainWindow", "清空"))
        self.menu.setTitle(_translate("MainWindow", "文件"))
        self.menu_3.setTitle(_translate("MainWindow", "退出"))
        self.menu_2.setTitle(_translate("MainWindow", "帮助 "))
        self.actionda.setText(_translate("MainWindow", "设置路径"))
        self.actionbaoc.setText(_translate("MainWindow", "保存"))
        self.about.setText(_translate("MainWindow", "关于"))

       #点击设置路径 弹出窗口
        self.actionda.triggered.connect(self.save_path)

        #点击退出
        self.actionbaoc.triggered.connect(self.closeEvent)

    def closeEvent(self):
        #是否退出
        reply = QMessageBox.question(self.MainWindow, '退出', '是否退出?', QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        if reply == QMessageBox.Yes:
            self.MainWindow.close()
            sys.exit(0)
        else :
            pass


    def save_path(self):
        #下载的路径会使用这里设置的，需要持久化存储
        settings = QSettings('MyCompany', 'MyApp')
        save_path = QFileDialog.getExistingDirectory(self.MainWindow, "选择保存路径")
        if save_path:
            settings.setValue('save_path', save_path)
            QMessageBox.information(self.MainWindow, '成功', '保存路径已设置!')

    def show_about_window(self):
        """显示关于窗口的方法"""
        # 创建 AboutWindow 的实例
        self.about_window = AboutWindow()
        # 显示关于窗口
        self.about_window.show()


def exception_hook(exctype, value, traceback):
    logging.error('Uncaught exception:', exc_info=(exctype, value, traceback))
    sys.__excepthook__(exctype, value, traceback)



if __name__ == '__main__':
    sys.excepthook = exception_hook
    app = QtWidgets.QApplication(sys.argv)
    window = MyWindow()
    window.show()
    sys.exit(app.exec())



